#include "main.h"

u8 atox(char c1, char c2) {
	u8 result, pos;
	char xalphabet[] = "0123456789abcdef";

	result = 0x00;
	/* Convert the first character into the 4 most significant bits. */
	for(pos = 0; pos < 16; ++pos)
		if(c1 == xalphabet[pos])
			break;
	result += pos*16;
	/* Convert the second character into the 4 least significant bits. */
	for(pos = 0; pos < 16; ++pos)
		if(c2 == xalphabet[pos])
			break;
	result += pos;

	return result;
}

int main(int argc, char **argv) {
	char *dict_path; // The directory of the dictionary file.
	char c; // Character used to detect input parameters.
	int mode; // Switch used to determine whether the program is running in dictionary or brute force mode.
	int blocks; // Number of blocks to be used.
	int threads; // Number of threads per block to be used.
	grid_size gs;
	dict_size ds;
	u8 *result_key; // Resulting key corresponding to target_hash
	u8 *target_hash; // Target hash converted in binary form on the GPU.
	u8 *salt; // Salt on the GPU.
	u8 *dict; // Dictionary on the GPU.
	int size; // Length of the passwords generated in brute force mode.
	char ch; // Character used to copy the dictionary file.
	char *s; // Character used to address the input salt. 
	u8 *host_salt; // Salt converted form char to u8.
	u8 *sp; // Used during salt input.
	FILE *in_file; // Pointer used to address the dictionary file.
	int index; // Generic index used in loops.
	int salt_len; // Length in bytes of the salt string.
	char *str_hash; // String containing the target hash.
	u8 *host_int_hash; // Used during conversion of the textual hash into the binary hash and to copy it on the GPU.
	struct timespec timer_s; // Timer used to detect the beginning of the breaking process.
	struct timespec timer_e; // Timer used to detect the end of the breaking process.
	long int timer_t; // Time used for the breaking process.

	/* Initialize parameters to defaults. */
	dict_path = NULL;
	mode = 0;
	size = 4;
	blocks = 1;
	threads = 1;
	str_hash = NULL;
	/* Get input parameters. */
	c = getopt(argc, argv, "d:m:b:j:i:s:l:");
	while(c != -1) {
		switch(c) {
			case 'd':
				dict_path = optarg;
				break;
			case 'm':
				mode = atoi(optarg);
				break;
			case 'b':
				blocks = atoi(optarg);
				break;
			case 'j':
				threads = atoi(optarg);
				break;
			case 'i':
				str_hash = optarg;
				break;
			case 's':
				/* The salt has to be read character by character because it has to be allocated as a u8*. */
				s = optarg;
				salt_len = 1;
				/* Detect length of salt string to properly allocate memory. */
				while(*s != '\0') {
					++salt_len;
					++s;
				}
				host_salt = (u8*) malloc(sizeof(u8)*salt_len);
				s = optarg;
				sp = host_salt;
				/* Copy salt to u8. */
				while(*s != '\0') {
					*sp = *s;
					++sp;
					++s;
				}
				*sp = '\0';
				break;
			case 'l':
				size = atoi(optarg);
				break;
			default:
				if(optopt == 'd' || optopt == 'm' || optopt == 'b' || optopt == 'j' || optopt == 'i' || optopt == 's' || optopt == 'l')
					printf("Option -%c requires an argument.\n", optopt);
				else if(isprint (optopt))
					printf("Unknown option `-%c'.\n", optopt);
				else
					printf("Unknown option character `\\x%x'.\n", optopt);
				return 1;
		}
		c = getopt(argc, argv, "d:m:b:j:i:s:l:");
	}

	/* If no salt has been given as input, default to string "". */
	if(host_salt == NULL) {
		host_salt = (u8*) malloc(sizeof(u8));
		*host_salt = '\0';
	}
	/* If no target hash has been given, terminate the program. */
	if(str_hash == NULL) {
		printf("No target hash has been given, terminating.\n");
		return 1;
	}
	
	#if DEBUG
	fprintf(stderr, "Options: dict_path %s, mode %d, blocks %d, threads %d, hash %s, salt %s, len %d\n",
		dict_path, mode, blocks, threads, str_hash, host_salt, size);
	#endif
	// Allocating space needed for the eventual result key.
	cudaMalloc((void**) &result_key, sizeof(u8)*MAX_PASSW_LENGTH);
	cudaMemset(result_key, '\0', sizeof(u8)*MAX_PASSW_LENGTH);

	gs.threads = threads;
	gs.blocks = blocks;

	/* If mode is dictionary, read dictionary file and allocate it on GPU memory. */
	if(mode == 0) {
		/* The dictionary file needs to be read character by character because it has to be allocated as a u8*. */
		in_file = fopen(dict_path, "r");
		if(in_file == NULL) {
			fprintf(stderr, "Error opening file: no such file or directory.\n");
			return 1;
		}
		/* Detect dimension of dictionary in order to allocate memory on the GPU. */
		fseek(in_file, 0L, SEEK_END);
		ds.len = ftell(in_file);
		cudaMalloc((void**) &dict, sizeof(u8)*ds.len);
		fseek(in_file, 0L, SEEK_SET);
		ch = fgetc(in_file);
		index = 0;
		ds.word_cnt = 0;
		/* Copy dictionary to GPU substituting every '\n' with a '\0' for string termination and count words. */
		while(ch != EOF) {
			if(ch == '\n') {
				++ds.word_cnt;
				cudaMemset(dict + index, '\0', sizeof(u8));
			} else
				cudaMemcpy(dict + index, &ch, sizeof(u8), cudaMemcpyHostToDevice);
			++index;
			ch = fgetc(in_file);
		}
		fclose(in_file);
	}

	/* Allocate memory for salt on the GPU and copy it from input. */
	cudaMalloc((void**) &salt, sizeof(u8)*salt_len);
	// cudaMemset(salt, '\0', sizeof(u8)*salt_len);
	cudaMemcpy(salt, host_salt, sizeof(u8)*salt_len, cudaMemcpyHostToDevice);

	/* Allocate memory for target hash on the GPU. */
	cudaMalloc((void**) &target_hash, sizeof(u8)*16);
	/* Allocate memory for target hash conversion. */
	host_int_hash = (u8*) malloc(sizeof(u8)*16);
	/* Convert target hash from hexadecimal text format to binary. */
	for(index = 0; index < 16; ++index) {
		host_int_hash[index] = atox(str_hash[2*index], str_hash[2*index + 1]);
	}
	/* Copy converted hash on the GPU. */
	cudaMemcpy(target_hash, host_int_hash, sizeof(u8)*16, cudaMemcpyHostToDevice);

	/* Detect time before the beginning of the breaking process. */
	clock_gettime(CLOCK_REALTIME, &timer_s);
	/* Launch the breaking process corresponding to the chosen mode. */
	switch(mode) {
		case 0:
			break_dictionary(gs, ds, dict, salt, target_hash, result_key);
			break;
		case 1:
		default:
			brute_force(size, gs, salt, target_hash, result_key);
			break;
	}
	/* Detect time after the end of the breaking process and compute elapsed time. */
	clock_gettime(CLOCK_REALTIME, &timer_e);
	timer_t = (timer_e.tv_sec - timer_s.tv_sec)*1000000000 + (timer_e.tv_nsec - timer_s.tv_nsec);
	printf("Breaking process time: %ld\n", timer_t);

	cudaFree(salt);
	cudaFree(dict);
	cudaFree(target_hash);
	cudaFree(result_key);

	return 0;
}
